﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;

namespace Microsoft.Internal.Collections
{
    internal static class CollectionServices
    {
        public static T[] CopyArrayInsertLeft<T>(T[] source, T item)
        {
            T[] newArray = new T[source.Length + 1];
            newArray[0] = item;

            source.CopyTo(newArray, 1);
            return newArray;
        }

        public static T[] CopyArrayRemoving<T>(T[] source, int index)
        {
            T[] newArray = new T[source.Length - 1];
            Array.Copy(source, 0, newArray, 0, index);
            if (index + 1 < source.Length)
            {
                Array.Copy(source, index + 1, newArray, index, newArray.Length - index);
            }
            return newArray;
        }

        public static void CopyTo<A, B>(IEnumerable<A> source, Func<A, B> filter, B[] array, int arrayIndex)
        {
            if (source == null) { throw new ArgumentNullException("source"); }
            if (array == null) { throw new ArgumentNullException("array"); }
            if (arrayIndex < 0) { throw new ArgumentOutOfRangeException("arrayIndex"); }
            if (source.Count() + arrayIndex > array.Length)
            {
                throw new ArgumentException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Strings.SequenceCopyToIndex,
                        source.Count(),
                        array.Length,
                        arrayIndex),
                    "arrayIndex");
            }

            foreach (A item in source)
            {
                array[arrayIndex] = filter(item);

                arrayIndex++;
            }
        }

        public static Type GetSequenceItemType(Type sequenceType)
        {
            if (!typeof(IEnumerable).IsAssignableFrom(sequenceType))
            {
                return null;
            }

            Type collectionInterface;
            if (ReflectionServices.TryGetGenericInterfaceType(sequenceType, typeof(IEnumerable<>), out collectionInterface))
            {
                return collectionInterface.GetGenericArguments()[0];
            }

            Type itemType;
            if (WeakCollection.IsPromotableWeakCollection(sequenceType, out itemType))
            {
                return itemType;
            }

            return typeof(object);
        }

        public static bool IsCollectionType(Type type, out Type itemType)
        {
            Type sequenceType;
            if (!TryGetNormalizedSequenceType(type, out sequenceType))
            {
                itemType = null;
                return false;
            }

            Type closedType;
            if (!ReflectionServices.TryGetGenericInterfaceType(sequenceType, typeof(ICollection<>), out closedType))
            {
                itemType = null;
                return false;
            }

            itemType = closedType.GetGenericArguments()[0];
            return true;
        }

        public static bool IsEnumerableType(Type type, out Type itemType)
        {
            Type sequenceType;
            if (!TryGetNormalizedSequenceType(type, out sequenceType))
            {
                itemType = null;
                return false;
            }

            Type closedType;
            if (!ReflectionServices.TryGetGenericInterfaceType(sequenceType, typeof(IEnumerable<>), out closedType))
            {
                itemType = null;
                return false;
            }

            itemType = closedType.GetGenericArguments()[0];
            return true;
        }

        public static T OneOrDefault<T>(this IEnumerable<T> source)
        {
            Assumes.NotNull(source, "source");

            using (var enumerator = source.GetEnumerator())
            {
                if (enumerator.MoveNext())
                {
                    T result = enumerator.Current;
                    if (!enumerator.MoveNext())
                    {
                        return result;
                    }
                }
            }

            return default(T);
        }

        public static bool TryGetNormalizedSequenceType(Type type, out Type sequenceType)
        {
            if (type == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(type))
            {
                sequenceType = null;
                return false;
            }

            if (ReflectionServices.TryGetGenericInterfaceType(type, typeof(IList<>), out sequenceType))
            {
                return true;
            }

            if (ReflectionServices.TryGetGenericInterfaceType(type, typeof(ICollection<>), out sequenceType))
            {
                return true;
            }

            if (typeof(IList).IsAssignableFrom(type))
            {
                sequenceType = typeof(IList<object>);
                return true;
            }

            Type itemType;
            if (WeakCollection.IsPromotableWeakCollection(type, out itemType))
            {
                sequenceType = typeof(ICollection<>).MakeGenericType(itemType);
                return true;
            }

            if (ReflectionServices.TryGetGenericInterfaceType(type, typeof(IEnumerable<>), out sequenceType))
            {
                return true;
            }

            sequenceType = typeof(IEnumerable<object>);
            return true;
        }

        public static Collection<T> ToCollection<T>(this IEnumerable<T> source)
        {
            Assumes.NotNull(source);

            return new Collection<T>(source.ToList());
        }

        public static IList CreateGenericList(Type elementType)
        {
            Assumes.NotNull(elementType);

            Type specializedListType = typeof(List<>).MakeGenericType(elementType);

            return (IList)Activator.CreateInstance(specializedListType);
        }

        public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T> source)
        {
            Assumes.NotNull(source);

            return source.Where(element => element != null);
        }

        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach(T t in source)
            {
                action.Invoke(t);
            }
        }

        public static EnumerableCardinality GetCardinality<T>(this IEnumerable<T> source)
        {
            Assumes.NotNull(source);

            using (var enumerator = source.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    return EnumerableCardinality.Zero;
                }

                if (!enumerator.MoveNext())
                {
                    return EnumerableCardinality.One;
                }

                return EnumerableCardinality.TwoOrMore;
            }
        }
    }
}